package owg.util.opengl;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;

import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL2;
import com.jogamp.opengl.GL3;

import owg.util.Calc;
import owg.util.data.Compression;
import owg.util.data.Pair;
import owg.util.euclidian.MatrixStack;
import owg.util.euclidian.V3F;

import com.jogamp.common.nio.Buffers;
/**Holds a 3D model loaded from a single-object wavefront obj file.
 * This implementation supports armature animation.
 * @see Armature*/
public class ObjModel
	{
	private static int ID_COUNTER = 0;
	
	/**
	 * This class is used as a key when messing with faces in the obj loader.
	 * More specifically, it is used to identify which tuples of (vertex,normal,texCoord) alreay exist.
	 * */
	public static class FaceKey
		{
		public int vertex,normal,texCoord;
		
		public FaceKey(int v, int n, int t)
			{
			vertex=v;
			normal=n;
			texCoord=t;
			}
		
		public boolean equals(Object o)
			{
			if (o instanceof FaceKey)
				return (((FaceKey) o).vertex==vertex && ((FaceKey) o).normal==normal && ((FaceKey) o).texCoord==texCoord);
			else
				return false;
			}
		public int hashCode()
			{
			return (vertex*16000+normal)*16000+texCoord;
			}
		}

	
	public static final byte VERTEX_MASK   = (byte)1;
	public static final byte NORMAL_MASK   = (byte)2;
	public static final byte TEXCOORD_MASK = (byte)4;
	public static final byte INDEX_MASK    = (byte)8;
	public static final byte TEXCOORD2_MASK= (byte)16;
	public static final byte ALL_MASK      = (byte)-1;
	
	private static HashMap<String,ObjModel> objModels = new HashMap<String,ObjModel>();
	/**This method looks for the model with the given file name.
	 * If it is already in memory, the first instance that was loaded is returned.
	 * The instance is then not bound to the armature in the parameter.
	 * Otherwise, it is loaded from disk, and bound to the supplied armature.
	 * If multiple instances are required, use softCopy on the returned instance.
	 * If armature is null, the model will use STATIC_DRAW instead of DYNAMIC_DRAW.
	 * @param mtllibOverride Normally null, can be used to explicitly specify which material library to use.
	 * Will not have any effect if the model is already loaded. In such a case, use setMaterial or use
	 * the static getUniqueInstance method.
	 * @param fileName A file name including the .obj extension, but not the directory path. If null, null will be returned.
	 * @param armature An armature to bind to the obj model. If the model should be used with an armature,
	 * this must be specified on the first load. If the model is already loaded, this argument will not have any effect.
	 * If the model is loaded and already bound to an armature, use the setArmature method to change this, or use
	 * the static getUniqueInstance method.*/
	public static ObjModel get(EGL<?> glUtil, String fileName,Armature armature, String mtllibOverride)
		{
		if(fileName == null)
			return null;
		if(objModels.containsKey(fileName))
			return objModels.get(fileName);
		else
			{
			ObjModel m = new ObjModel(glUtil, fileName,armature,mtllibOverride);
			objModels.put(fileName, m);
			return m;
			}
		}
	/**Remove the model with the given filename from the hashmap of models.
	 * This will allow the garbage collector to deallocate the model(the GL Buffers, if any, will also be destroyed automatically.)
	 * It can also be used to force the next get() call to reload the model, e.g. if the file was edited.
	 * @return the model that was removed.*/
	public static ObjModel remove(String fileName)
		{
		ObjModel m = objModels.remove(fileName);
		if(m != null)
			StaticModel.removeStaticModel(fileName);
		return m;
		}
	
	/**Convenience method for a concatenated get() and getUnusedInstance().
	 * Null material will restore the original material. 
	 * Null file name will cause null to be returned.
	 * Armatures cannot be bound to models initially loaded without armature.
	 * Calling with null armature will restore the model's vertices.
	 * @see ObjModel#get(String, Armature, String)
	 * @see ObjModel#getUnusedInstance(ObjModel, Armature, String, boolean)*/
	public static ObjModel getUnusedInstance(EGL<?> glUtil, String fname, Armature arm, String material, boolean setToInUse)
		{
		ObjModel m = ObjModel.get(glUtil, fname, arm, material);
		return getUnusedInstance(m, arm, material, setToInUse);
		}
	/**
	 * This method will take the indicated model and make sure that 
	 * -An instance that is not in use by another object is returned
	 * (The returned instance may be the same as the source)
	 * -The returned instance is bound to the indicated armature. 
	 * Note that this will have undefined results if the armature does not have identical bones.
	 * -The returned instance has the materials from the indicated material library(a file name, without directory).<br/>
	 * <br/>
	 * Note that if the input model is null, null is returned by this method.<br/>
	 * Passing null as material will restore the model to its original coat.
	 * Calling with null armature will restore the model's vertices.
	 */
	public static ObjModel getUnusedInstance(ObjModel mod, Armature arm, String material, boolean setToInUse)
		{
		if(mod == null)
			return null;
		
		if(mod.isInUse)
			mod = mod.softCopy(arm, material);
		else 
			{
			if(material != null && !mod.getMaterial().equals(material))
				mod.setMaterial(material);
			else if(material == null && !mod.getMaterial().equals(mod.getDefaultMTLLib()))
				mod.setMaterial(mod.getDefaultMTLLib());
			mod.setArmature(arm);
			}
		if(arm == null)
			mod.updateToArmature();
		if(setToInUse)
			mod.isInUse = true;
		mod.restoreTexCoords();
		mod.useMaterials = true;
		return mod;
		}
	
	private final EGL<?> glUtil;
	/**Name of (first) object found in the file*/
	private String objName;
	public final String fname;
	/**An unique ID for this model. Changes with file versions reloaded at runtime, but not with soft copies.*/
	public final int id;
	public final boolean isOriginal;
	/**Indicates when the object has been disposed. Used only for consistency checking.i*/
	boolean isDisposed = false;
	/**Indicates that this Model borrows buffers and data from another Model. Is null when this is the original.
	 * This reference will prevent the garbage collector from finalizing the source object, which is what we need.*/
	ObjModel dataSrc = null;
	String mtllib;
	private String defaultMTLLib;
	HashMap<String,Integer> groupMap;//Useful when parsing the bvh files
	String[] groupNames;//Useful for debug 
	int[] groupIndices;//Mapping obj file indices to armature file indices
	
	float[] vertices;
	float[] transformedVertices;
	int  [][] vertexGroups;//These are not flattened.
	float[][] vertexGroupInfluence;//Normalize in post-processing
	
	float[] normals;//Normalize during reading
	float[] transformedNormals;
	
	float[] texCoords;
	float[] texCoords2;
	int nTexCoords;
	int nTexCoords2 = 0;
	
	int  [] faceTuples;
	int  [] edgePairs;
	
	Material [] faceMaterials;
	int      [] faceMaterialIndices;
	
	Armature armature;
	
    /**Pointer to the indices of the VBOs*/
    int vertexVbo = -1;
	int normalVbo = -1;
	int texCoordVbo = -1;
	int texCoordVbo2 = -1;
	int indexVbo = -1;
	boolean vbosBound = false;
	boolean texCoordVbo2Bound = false;
	/**The buffers containing the data*/
    FloatBuffer vertexData;
    FloatBuffer normalData;
    FloatBuffer texCoordData;
    FloatBuffer texCoordData2;
    IntBuffer indexData;

    /**The total space of a single vertex with all its data*/
    int vertexStride;
    /**The position of the color data in the vertex data*/
    int colorPointer;
    /**The position of the coordinate data in the vertex data*/
	int vertexPointer;
	/**The position of the texture data in the vertex data*/
	int texCoordPointer;
	/**The position of the texture data(multitexture slot 2) in the vertex data*/
	int texCoordPointer2;
	
	/**Flag set to true when the VBO or Direct Buffer needs to be reloaded due to a deformation*/
	private boolean vboOutdated;
	/**Whether to render with materials*/
	public boolean useMaterials;
	/**Provides a hint as to whether this instance is being used at the moment.
	 * Strive to set this to false when the user is destroyed.
	 * The value of this field is managed by users of the class, 
	 * it is not used internally, and is initially false.*/
	public boolean isInUse;
	/**
	 * Key: obj file name without path.
	 * Value: a Pair consisting of
	 * a: The dimensionality of the texture coordinates(typically 2)
	 * b: A compressed float array with texture coordinates for the obj Model with that name.
	 * 
	 * The key-value pair for a model is defined iff:
	 * Some user of the indicated object model has redefined the texture coordinates.
	 */
	private static final HashMap<String, Pair<Integer, byte[]>> texCoordBackups = new HashMap<String, Pair<Integer,byte[]>>();
	
	private ObjModel(EGL<?> glUtil, int id, String fname, boolean original)
		{
		this.id = id;
		this.isOriginal = true;
		this.glUtil = glUtil;
		this.fname = fname;
		}
	
	/**Load the obj from the file in the model directory. 
	 * If an armature is supplied, it attempts to bind the vertices to the bones.
	 * Vertex group data must be supplied in the obj file through my modified obj exporter.
	 * If armature is null, the model will use STATIC_DRAW instead of DYNAMIC_DRAW.
	 * @param mtllibOverride Normally null, can be used to explicitly specify which material library to use.*/
	private ObjModel(EGL<?> glUtil, String fname, Armature a, String mtllibOverride)
		{
		this(glUtil, ID_COUNTER++, fname, true);
		dataSrc = null;
		isDisposed = false;
		isInUse = false;
		useMaterials=true;
		armature = a;
		groupMap = new HashMap<String,Integer>();
		mtllib = mtllibOverride;
		//Initialize temporary data structures to ease parsing
		Queue<String> groupNameQ = new LinkedList<String>(); 
		int groupIndex=0;
		
		//This is an arraylist because we need look up in it for calculating missing normals:
		ArrayList<float[]> vertexQ = new ArrayList<float[]>();
		Queue<int[]> vertexGroupQ = new LinkedList<int[]>();
		Queue<float[]> vertexGroupInfluenceQ = new LinkedList<float[]>();
		vertexQ.add(new float[]{9999,9999,9999});//Note: All face definitions have vertex indices. This is not used.);
		vertexGroupQ.add(null);//This is just for consistency
		vertexGroupInfluenceQ.add(null);
		int vertexIndex=1;
		
		Queue<float[]> normalQ = new LinkedList<float[]>();
		normalQ.offer(new float[]{0,0,1});//This is not used. Normals are calculated instead.
		int normalIndex=1;
		
		Queue<float[]> texCoordQ = new LinkedList<float[]>();
		texCoordQ.offer(new float[]{0,0,0});//This may be used.
		int texCoordIndex=1;
		
		Queue<int[]> edgeQ = new LinkedList<int[]>();
		Queue<int[]> faceVertexQ = new LinkedList<int[]>();
		Queue<int[]> faceTexCoordQ = new LinkedList<int[]>();
		Queue<int[]> faceNormalQ = new LinkedList<int[]>();
		int faceIndex=0;
		int numQuads =0;//Remember number of quads we have triangulated.
		
		ArrayList<String> faceMaterialList = new ArrayList<String>();
		Queue<Integer> faceMaterialIndexQ = new LinkedList<Integer>(); 
		int faceMaterialIndex=0;
		
		File file = new File("res/models/"+fname);
		FileReader fr = null;
		BufferedReader br = null;
		try//Setup readers
			{
			fr = new FileReader(file);
			br = new BufferedReader(fr);
			
			
			String line;
			while((line=br.readLine())!=null)
				{
				if(line.startsWith("o"))
					{
					//Hack: Vertex group names
					line = Mtllib.strip(line,1);
					objName=line;
					}
				else if(line.startsWith("#@N"))
					{
					//Hack: Vertex group names
					line = Mtllib.strip(line,3);
					groupMap.put(line, groupIndex);
					groupNameQ.offer(line);
					groupIndex++;
					}
				else if(line.startsWith("vt"))
					{
					//TexCoord definitions
					line = Mtllib.strip(line,2);
					String[] split = line.split(" ");
					float[] tc = new float[split.length];
					for(int i=0; i<split.length; i++)
						{
						tc[i] = Float.parseFloat(split[i]);
						}
					tc[1]=1f-tc[1];//It's upside down for no apparent reason
					texCoordQ.offer(tc);
					texCoordIndex++;
					}
				else if(line.startsWith("vn"))
					{
					//Normal definitions
					line = Mtllib.strip(line,2);
					String[] split = line.split(" ");
					float[] n = new float[split.length];
					for(int i=0; i<split.length; i++)
						{
						n[i] = Float.parseFloat(split[i]);
						}
					normalQ.offer(n);
					normalIndex++;
					}
				else if(line.startsWith("f"))
					{
					//face definitions
					line = Mtllib.strip(line,1);
					boolean skip = false;
					//split into individual vertex definitions
					String[] split = line.split(" ");
					if(split.length==4)//crappiest triangulation(creates a double entry, remember number of double entries):
						{
						split = new String[]{split[0],split[1],split[2],split[2],split[3],split[0]};
						numQuads++;
						}
					else if(split.length == 2)
						{
						//Edge(will not be rendered, may be used as metadata by the application)
						edgeQ.add(new int[]{Integer.parseInt(split[0]), Integer.parseInt(split[1])});
						skip = true;
						}
					else if(split.length != 3)
						{
						System.err.println("Warning: Illegal number of vertices in face definition: "+split.length+
								"(not a triangle or quad)");
						skip = true;
						}
					
					if(!skip)
						{
						int[] fv = new int[split.length];
						int[] ftc = new int[split.length];
						int[] fn = new int[split.length];
						
						int fabricatedNormalIndex=-1;
						//Handle each vertex in the face definition
						for(int i=0; i<split.length; i++)
							{
							if(i==0 || i==3)
								fabricatedNormalIndex=-1;
							
							String[] v = split[i].split("/",-1);
							for(int j=0; j<3; j++)
								{
								int index;
								if(j>=v.length || v[j].equals(""))
									index = 0;//Default value(e.g. for undefined texcoord)
								else
									index = Integer.parseInt(v[j]); //Remember: this is 1-indexed by definition.
									//This is OK because 0 is reserved for a default value!
								
								if (j==0)
									{
									//Vertex index
									fv[i]=index;
									}
								else if (j==1)
									{
									//TexCoord index
									ftc[i] = index;
									}
								else if (j==2)
									{
									//Normal index
									//Note: The calculation only works if all vertices and normals are defined first.
									if(index==0)
										{
										if(fabricatedNormalIndex==-1)//Reserve an index
											{
											fabricatedNormalIndex = normalIndex; 
											normalIndex++;
											}
										//Note: the actual normal must be computed below, after the vertices are parsed.
										index=fabricatedNormalIndex;
										}
									if(fabricatedNormalIndex!=-1 && (i==2 || i==5))//Actually calculate the normal if required
										{
										V3F normal =          new V3F(vertexQ.get(fv[i]),0).add(new V3F(vertexQ.get(fv[i-2]),0), -1);
										normal.setCross(new V3F(vertexQ.get(fv[i]),0).add(new V3F(vertexQ.get(fv[i-1]),0), -1));
										normalQ.offer(normal.p);//It's unitized later, so whatever
										}
									fn[i] = index;
									}
								}
							}
						
						faceVertexQ.offer(fv);
						faceTexCoordQ.offer(ftc);
						faceNormalQ.offer(fn);
						faceIndex++;
						}
					}
				else if (line.startsWith("v"))
					{
					//Vertex definition
					
					//Check for a vertex group definition(my personal hack)
					boolean added = false;
					int groupDefIndex = line.indexOf("#@");
					if (groupDefIndex!=-1)
						{
						String groupDef = Mtllib.strip(line,groupDefIndex+2);
						if(!groupDef.equals(""))
							{
							String[] split = groupDef.split(" ");
							int[] vg = new int[split.length/2];
							float[] vgi = new float[split.length/2];
							for(int i=0; i<split.length; i+=2)
								{
								vg[i/2] = Integer.parseInt(split[i]);
								vgi[i/2] = Float.parseFloat(split[i+1]);
								}
							vertexGroupQ.offer(vg);
							vertexGroupInfluenceQ.offer(vgi);
							added=true;
							}
						}
					if(!added)//Must add something
						{
						vertexGroupQ.offer(null);
						vertexGroupInfluenceQ.offer(null);
						}
					line = Mtllib.strip(line,1);//This also strips the hack from the line.
					String[] split = line.split(" ");
					float[] v = new float[split.length];
					for(int i=0; i<split.length; i++)
						{
						v[i] = Float.parseFloat(split[i]);
						}
					vertexQ.add(v);
					vertexIndex++;
					}
				else if (line.startsWith("mtllib"))
					{
					defaultMTLLib = Mtllib.strip(line, 6);
					if(mtllibOverride == null)
						mtllib = defaultMTLLib;
					}
				else if (line.startsWith("usemtl") && mtllib!=null)
					{
					String mtlName = Mtllib.strip(line, 6);
					
					//System.out.println(mtllib+", "+MTL.strip(line, 6));
					faceMaterialList.add(mtlName);
					faceMaterialIndexQ.offer((faceIndex+numQuads)*3);//number of triangles. (uh, pretty sure this is number of vertices...)
					faceMaterialIndex++;
					}
				}
			}
		catch (IOException e)//Print info for debugging if anything fails
			{
			System.err.println("Model loader fail: "+file);
			e.printStackTrace();
			}
		catch (NumberFormatException e)
			{
			System.err.println("Model loader fail: "+file);
			e.printStackTrace();
			}
		finally//Close readers if they were opened at all
			{
			try {br.close();
				fr.close();}
			catch (Exception e){}
			}
		
		
		//Post-processing:
		int c;
		groupNames = new String[groupIndex];
		groupIndices = new int[groupIndex];
		for(int i=0; i<groupIndex; i++)
			{
			groupNames[i] = groupNameQ.poll();
			
			Bone b = null; 
			if(armature!=null)
				{
				b = armature.getBone(groupNames[i]);
				}	
			if(b==null)
				groupIndices[i]=-1;
			else
				groupIndices[i] = b.index;
			}
		
		
		//These values are NOT FINAL. 
		//They must be redefined later down because OpenGL does not support multiple IBOs on one mesh:
		vertices = new float[vertexIndex*3];
		vertexGroups = new int[vertexIndex][];
		vertexGroupInfluence = new float[vertexIndex][];
		c=0;
		for(int i=0; i<vertexIndex; i++)
			{
			float[] v = vertexQ.get(i);
			
			for(int j=0; j<3; j++)
				{
				vertices[c] = v[j];
				c++;
				}
			
			int[] vg = vertexGroupQ.poll();
			if(vg==null)
				vg = new int[0];
			vertexGroups[i] = vg;
			float[] vgi = vertexGroupInfluenceQ.poll();
			if(vgi==null)
				vgi = new float[0];
			vertexGroupInfluence[i] = vgi;
			
			if (vertexGroups[i].length>0 && armature!=null)//Normalization, if appliccable
				{
				float total = 0;
				for(int j=0; j<vertexGroups[i].length; j++)
					{
					int vGroup = vertexGroups[i][j];
					if(vGroup < 0 || vGroup >= groupNames.length)
						{
						System.err.println("Warning: "+this+" contains references to undefined vertex group: "+vGroup);
						vertexGroups[i][j] = 0;
						vertexGroupInfluence[i][j] = 0;
						}
					else
						{
						if(armature.getBone(groupNames[vGroup])==null)
							{}//vertexGroupInfluence[i][j]=0;//Missing bone in armature
						else
							total+=vertexGroupInfluence[i][j];
						}
					}
				for(int j=0; j<vertexGroups[i].length; j++)
					vertexGroupInfluence[i][j]/=total;
				}
			}
		vertexQ = new ArrayList<float[]>();//Empty it
		vertexQ.add(new float[]{9999,9999,9999});//Readd default values
		vertexGroupQ.add(null);
		vertexGroupInfluenceQ.add(null);
		
		normals = new float[normalIndex*3];
		c=0;
		for(int i=0; i<normalIndex; i++)
			{
			float[] n = Calc.normalize(normalQ.poll());
			for(int j=0; j<3; j++)
				{
				normals[c] = n[j];
				c++;
				}
			}
		normalQ.offer(new float[]{0,0,1});//Readd the default normal
		
		texCoords = new float[texCoordIndex*2];
		nTexCoords = 2;
		texCoords2 = null;
		nTexCoords2 = 0;
		c=0;
		for(int i=0; i<texCoordIndex; i++)
			{
			float[] t = texCoordQ.poll();
			for(int j=0; j<2; j++)
				{
				texCoords[c] = t[j];
				c++;
				}
			}
		texCoordQ.offer(new float[]{0,0,0});//Readd the default texCoord

		String oldMtllib = mtllib;
		
		if(!Mtllib.exists(mtllib))
			{
			mtllib = Mtllib.getFirstCompatibleMtllib(faceMaterialList);
			System.out.println(this+": "+oldMtllib+"->"+mtllib);
			if(mtllib != null && oldMtllib != null && oldMtllib.equals(defaultMTLLib))
				defaultMTLLib = mtllib;
			}
		
		if(!Mtllib.isCompatible(mtllib, faceMaterialList))
			{
			System.err.println("Warning from "+this+": "+mtllib+" is not compatible with: "+
						Arrays.toString(faceMaterialList.toArray()));
			mtllib = "default.mtl";
			for(int i = 0; i<faceMaterialList.size(); i++)
				faceMaterialList.set(i, "Material");
			if (oldMtllib.equals(defaultMTLLib))
				defaultMTLLib = mtllib;
			}
		
		//Get material indexing
		faceMaterials = new Material[faceMaterialIndex];
		faceMaterialIndices = new int[faceMaterialIndex];
		for(int i=0; i<faceMaterialIndex; i++)
			{
			faceMaterials[i] = Mtllib.getMaterial(mtllib, faceMaterialList.get(i));
			if(faceMaterials[i]==null)
				System.err.println("Material error!");
			faceMaterialIndices[i] = faceMaterialIndexQ.poll();
			}
		
		int uniqueTuples=1;
		c=0;
		faceTuples = new int[(faceIndex+numQuads)*3];//Keep track of entries 
		HashMap<FaceKey,Integer> faces = new HashMap<FaceKey,Integer>();//Keep track of which tuples we have and where they are
		HashMap<Integer,Integer> vertMap;
		if(!edgeQ.isEmpty())
			vertMap = new HashMap<Integer,Integer>();//Map old vertex indices to new vertex indices for edge list generation
		else
			vertMap = null;
		//int nextMaterial = 0;
		for(int i=0; i<faceIndex; i++)
			{
			//These are 1-indexed, with 0 for default value
			
			/*We need to deal with indexing:
			 * Each unique vertex/normal/texCoord/material tuple needs to have its own index 
			 * We have everything listed above.
			 * We are going to readd them to their queues whenever a new tuple is discovered.
			 * */
			
			int[] fv = faceVertexQ.poll();
			int[] ft = faceTexCoordQ.poll();
			int[] fn = faceNormalQ.poll();
			for(int j=0; j<fv.length; j++)
				{
				int v = fv[j];
				int n = fn[j];
				int t = ft[j];
				FaceKey key = new FaceKey(v,n,t);
				Integer position = faces.get(key);
				
				if(position==null)//If new
					{
					position = uniqueTuples;
					faces.put(key, position);
					if(vertMap != null)
						vertMap.put(v, position);//We just need some reference to the vertex coordinates
					vertexQ.add(new float[]{vertices[v*3],vertices[v*3+1],vertices[v*3+2]});
					vertexGroupQ.add(vertexGroups[v]);
					vertexGroupInfluenceQ.add(vertexGroupInfluence[v]);
					normalQ.offer(new float[]{normals[n*3],normals[n*3+1],normals[n*3+2]});
					texCoordQ.offer(new float[]{texCoords[t*2],texCoords[t*2+1]});
					uniqueTuples++;
					}
				faceTuples[c] = position;
				
				c++;
				}
			}
		
		//Compact edges into array
		edgePairs = new int[edgeQ.size()*2];
		int[] edge;
		for(int i = 0; (edge = edgeQ.poll())!=null; i+=2)
			{
			for(int j = 0; j<2; j++)
				{
				int v = edge[j];
				Integer position = vertMap.get(v);
				if(position == null)
					{
					if(position==null)//If new
						{
						position = uniqueTuples;
						vertMap.put(v, position);
						vertexQ.add(new float[]{vertices[v*3],vertices[v*3+1],vertices[v*3+2]});
						vertexGroupQ.add(vertexGroups[v]);
						vertexGroupInfluenceQ.add(vertexGroupInfluence[v]);
						normalQ.offer(new float[]{0,0,1});//dummy, trade memory for simplicity
						texCoordQ.offer(new float[]{0,0});//dummy too
						uniqueTuples++;
						}
					}
				edgePairs[i+j] = position;
				}
			}
		
		//The following have a default value at position 0. These values were added before.
		int vertexCnt = vertexQ.size();
		vertices = new float[vertexCnt*3];
		normals = new float[vertexCnt*3];
		texCoords = new float[vertexCnt*2];
		vertexGroups = new int[vertexCnt][];
		vertexGroupInfluence = new float[vertexCnt][];
		
		int d=0;
		c=0; 
		for(int i=0; i<vertexCnt; i++)
			{
			float[] v = vertexQ.get(i);
			float[] n = normalQ.poll();
			float[] t = texCoordQ.poll();

			for(int j=0; j<3; j++)
				{
				vertices[c] = v[j];
				normals[c] = n[j];
				if(j<2)
					{texCoords[d] = t[j];
					d++;}
				c++;
				}
			
			int[] vg = vertexGroupQ.poll();
			if(vg==null)
				vg = new int[0];
			vertexGroups[i] = vg;
			float[] vgi = vertexGroupInfluenceQ.poll();
			if(vgi==null)
				vgi = new float[0];
			vertexGroupInfluence[i] = vgi;//(already normalized.)
			}
		transformedVertices = vertices.clone();
		transformedNormals = normals.clone();
		setupBuffers();
        bindBuffers(ALL_MASK);
        vboOutdated=false;
		}

	/**Get the reference to the  array of untransformed vertices. This is a flat array, 3 components per vertex.
	 * Changing this array will result in the VBO being updated on the next armature update call, but is generally not advised.
	 * Note that due to the 1-indexation of wavefront obj files, this array contains a dummy vertex (9999,9999,9999) at the first element.*/
	public float[] getVertices()
		{
		return vertices;
		}
	
	/**Get the reference to the array of edge vertex indices. This is not used internally.*/
	public int[] getEdges()
		{
		return edgePairs;
		}
	/**Get the names of the vertex groups of this model.*/
	public String[] getVertexGroups()
		{
		return groupNames;
		}
	/**Get the reference to the  array of vertex group memberships for each vertex. This is an array of arrays of indices.
	 * The indices refer to the Strings in the array returned by getVertexGroups. 
	 * Changing this array will result in the VBO being updated on the next armature update call, but is generally not advised.*/
	public int[][] getVertexGroupMemberships()
		{
		return vertexGroups;
		}
	/**Get the reference to the  array of vertex group weights for each vertex. This is an array of arrays of weights.
	 * The indices in the subarray correspond to those in the array returned by getVertexGroupMemberships. 
	 * Changing this array will result in the VBO being updated on the next armature update call, but is generally not advised.*/
	public float[][] getVertexGroupInfluences()
		{
		return vertexGroupInfluence;
		}
	/**Get the reference to the  array of transformed vertices. This is a flat array, 3 components per vertex.
	 * Changing this array will not have any desirable direct effect.
	 * Note that due to the 1-indexation of wavefront obj files, this array contains a dummy vertex (9999,9999,9999) at the first element.*/
	public float[] getTransformedVertices()
		{
		return transformedVertices;
		}
	/**Get the reference to the  array of indices. This is a flat array, 3 components per triangle.
	 * Changing this array will result in the VBO being updated on the next armature update call, but is generally not advised.*/
	public int[] getTriangleIndices()
		{
		return faceTuples;
		}
	/**Get the reference to the  array of untransformed normals. This is a flat array, 3 components per vertex normal.
	 * Changing this array will result in the VBO being updated on the next armature update call, but is generally not advised.*/
	public float[] getNormals()
		{
		return normals;
		}
	/**Get the reference to the  array of transformed normals. This is a flat array, 3 components per vertex normal.
	 * Changing this array will not have any desirable direct effect.*/
	public float[] getTransformedNormals()
		{
		return transformedNormals;
		}
	/**Get the reference to the array of texture coordinates. The texture coordinates are never transformed. 
	 * This is a flat array, with nTexCoords(see: getNTexCoords) components per vertex.
	 * Changing this array will not have any desirable direct effect.*/
	public float[] getTexCoords()
		{
		return texCoords;
		}
	/**Get the reference to the array of texture coordinates(texture slot 2). The texture coordinates are never transformed. 
	 * This is a flat array, with nTexCoords(see: getNTexCoords) components per vertex. This will usually be null.
	 * Changing this array will, as a general rule, not have any desirable direct effect.*/
	public float[] getTexCoords2()
		{
		return texCoords2;
		}
	/**Get the number of texture coordinate components per vertex*/
	public int getNTexCoords()
		{return nTexCoords;}
	/**Get the number of texture coordinate components per vertex(texture slot 2)*/
	public int getNTexCoords2()
		{return nTexCoords2;}
	/**Set the texture coordinate array. This will update the vertex buffer object for the texture coordinate data.
	 * Note that texCoords can not normally be transformed, so this will, if deemed necessary internally, create
	 * a backup of the original texture coordinates. The user of this method must call restoreTexCoords() when it 
	 * no longer uses the object or wishes to restore the texture coordinates!
	 * @see ObjModel#restoreTexCoords()*/
	public void setTexCoords(float[] textureCoordinates, int componentsPerVertex)
		{
		if(!texCoordBackups.containsKey(fname))
			texCoordBackups.put(fname, new Pair<Integer,byte[]>(nTexCoords, Compression.compress(texCoords)));
		
		nTexCoords = componentsPerVertex;
		texCoords = textureCoordinates;
		texCoordData = glUtil.asBuffer(texCoords);
		bindBuffers(TEXCOORD_MASK);
		}

	public void restoreTexCoords()
		{
		if(texCoordBackups.containsKey(fname))
			{
			Pair<Integer, byte[]> p = texCoordBackups.get(fname);
			setTexCoords(Compression.decompress(p.b), p.a);
			}
		}
	
	/**Set the texture coordinate array(slot 2). This will update the vertex buffer object for the texture coordinate data.
	 * Note that a Model normally only has one set of texture coordinates. The user of the model object must specify
	 * with OpenGL calls what to do with the second multitexture slot, or it will not have any effect. There is also
	 * no backup system for the texture coordinates for this slot.*/
	public void setTexCoords2(float[] textureCoordinates, int componentsPerVertex)
		{
		nTexCoords2 = componentsPerVertex;
		texCoords2 = textureCoordinates;
		texCoordData2 = glUtil.asBuffer(texCoords2);
		bindBuffers(TEXCOORD2_MASK);
		}
	/**Create a copy without duplicating static data(original vertices, original normals, the texcoords and indices)
	 * @param armature May be null, binds the model to an armature.
	 * @param mtllibOverride Normally null, can be used to explicitly specify which material library to use.*/
	public ObjModel softCopy(Armature armature, String mtllibOverride)
		{
		ObjModel r = new ObjModel(glUtil, id, fname, false);
		r.dataSrc = this;
		r.isDisposed = false;
		
		r.objName = objName;
		r.groupMap = groupMap;
		r.groupNames = groupNames; 
		r.groupIndices = groupIndices;
		r.vertices = vertices;
		r.transformedVertices = transformedVertices.clone();
		r.vertexGroups = vertexGroups;
		r.vertexGroupInfluence = vertexGroupInfluence;
		r.normals = normals;
		r.transformedNormals = transformedNormals.clone();
		r.texCoords = texCoords;
		r.nTexCoords = nTexCoords;
		r.texCoords2 = texCoords2;
		r.nTexCoords2 = nTexCoords2;
		r.faceTuples = faceTuples;
		r.edgePairs = edgePairs;
		r.defaultMTLLib = defaultMTLLib;
		if(mtllibOverride == null)
			mtllibOverride = defaultMTLLib;
		r.mtllib = mtllibOverride;
		r.faceMaterials = new Material[faceMaterials.length];
		for(int i=0; i<faceMaterials.length; i++)
			{
 			r.faceMaterials[i] = Mtllib.getMaterial(mtllibOverride, faceMaterials[i].name);
			}
		r.faceMaterialIndices = faceMaterialIndices;
		r.armature = armature;
    	r.vertexStride = vertexStride;
    	r.colorPointer = colorPointer;
		r.vertexPointer = vertexPointer;
		r.texCoordPointer = texCoordPointer;
		r.texCoordPointer2 = texCoordPointer2;
		r.useMaterials = useMaterials;
		r.setupBuffers();
		r.bindBuffers(ALL_MASK);
        r.vboOutdated=false;
        r.isInUse = false;
		return r;
		}
	
	public Material getMaterialForFace(int faceIndex)
		{
		int matIndex = faceMaterialIndices.length-1;
		while(faceMaterialIndices[matIndex] > faceIndex*3)
			matIndex--;
		return faceMaterials[matIndex];
		}
	
	private void setupBuffers()
		{
		if(useVBO())
			{
			GL gl = glUtil.gl;
			// generate VBO handles
			IntBuffer buf = Buffers.newDirectIntBuffer(5);
			gl.glGenBuffers(5, buf);
			vertexVbo = buf.get();
			normalVbo = buf.get();
			texCoordVbo = buf.get();
			texCoordVbo2 = buf.get();
			indexVbo = buf.get();
			}
	    // generate data buffers
	    vertexData = glUtil.asBuffer(transformedVertices);
	    normalData = glUtil.asBuffer(transformedNormals);
	    texCoordData = glUtil.asBuffer(texCoords);
	    if(texCoords2 != null)
	    	texCoordData2 = glUtil.asBuffer(texCoords2);
	    indexData = glUtil.asBuffer(faceTuples);
		}
	
	private boolean useVBO()
		{
		return glUtil.getGL().isGL3() || glUtil.getPreferVBO();
		}
	private void bindBuffers(byte mask)
		{
		if(useVBO())
			{
			GL gl = glUtil.getGL();
			// transfer data to VBO
			if((mask&VERTEX_MASK)!=0)
				{
		        gl.glBindBuffer(GL.GL_ARRAY_BUFFER, vertexVbo);
		        gl.glBufferData(GL.GL_ARRAY_BUFFER, vertexData.capacity()*Calc.bytesPerFloat, vertexData, armature==null?GL.GL_STATIC_DRAW:GL.GL_DYNAMIC_DRAW);
				}
			if((mask&NORMAL_MASK)!=0)
				{
			    gl.glBindBuffer(GL.GL_ARRAY_BUFFER, normalVbo);
			    gl.glBufferData(GL.GL_ARRAY_BUFFER, normalData.capacity()*Calc.bytesPerFloat, normalData, armature==null?GL.GL_STATIC_DRAW:GL.GL_DYNAMIC_DRAW);
				}
			if((mask&TEXCOORD_MASK)!=0)
	        	{
		        gl.glBindBuffer(GL.GL_ARRAY_BUFFER, texCoordVbo);
		        gl.glBufferData(GL.GL_ARRAY_BUFFER, texCoordData.capacity()*Calc.bytesPerFloat, texCoordData, GL.GL_STATIC_DRAW);
	        	}
			if((mask&TEXCOORD2_MASK)!=0 && texCoordData2 != null)
	        	{
		        gl.glBindBuffer(GL.GL_ARRAY_BUFFER, texCoordVbo2);
		        gl.glBufferData(GL.GL_ARRAY_BUFFER, texCoordData2.capacity()*Calc.bytesPerFloat, texCoordData2, GL.GL_STATIC_DRAW);
		        texCoordVbo2Bound = true;//First bind happens at different time from the rest
	        	}
			if((mask&INDEX_MASK)!=0)
	        	{
		    	gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, indexVbo);
		    	gl.glBufferData(GL.GL_ELEMENT_ARRAY_BUFFER, indexData.capacity()*Calc.bytesPerInt, indexData, GL.GL_STATIC_DRAW);
	        	}
			gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
			vbosBound = true;
			}
		}
	/**
	 * Creates a transformed model from the original model by applying the model's armature, 
	 * and update the VBO or vertex array.
	 * The armature should be supplied with the constructor. 
	 * You should update the armature first in order for this to have any effect.
	 * Calling this with no bound armature will revert the vertices to their original state.
	 * @see ObjModel#update()
	 * */
	public void updateToArmature()
		{
		if(armature!=null)
			{
			//Ignore dummy vertex
			float[] n = new float[3];
			float[] v = new float[3];
			
			for(int i=3; i<transformedVertices.length; i+=3)
				{
				transformedVertices[i]=0;
				transformedVertices[i+1]=0;
				transformedVertices[i+2]=0;
				transformedNormals[i]=0;
				transformedNormals[i+1]=0;
				transformedNormals[i+2]=0;
				
				for(int j=0; j<vertexGroups[i/3].length; j++)
					{
					if(groupIndices[vertexGroups[i/3][j]]>=0)
						{
						Bone b = armature.bones[groupIndices[vertexGroups[i/3][j]]];
						if(b!=null)
							{
							n[0]=normals[i];
							n[1]=normals[i+1];
							n[2]=normals[i+2];
							Calc.rotationMatrixPostMultiplyOpt(b.units,n);
							
							v[0]=vertices[i]-b.undeformedPosition[0];
							v[1]=vertices[i+1]-b.undeformedPosition[1];
							v[2]=vertices[i+2]-b.undeformedPosition[2];
							Calc.rotationMatrixPostMultiplyOpt(b.units,v);

							transformedVertices[i]  +=(b.currentPosition[0]+v[0])*vertexGroupInfluence[i/3][j];
							transformedVertices[i+1]+=(b.currentPosition[1]+v[1])*vertexGroupInfluence[i/3][j];
							transformedVertices[i+2]+=(b.currentPosition[2]+v[2])*vertexGroupInfluence[i/3][j];
							
							transformedNormals[i]  +=n[0]*vertexGroupInfluence[i/3][j];
							transformedNormals[i+1]+=n[1]*vertexGroupInfluence[i/3][j];
							transformedNormals[i+2]+=n[2]*vertexGroupInfluence[i/3][j];
							}
						}
					}
				}
			}
		else
			{
			//Ignore dummy vertex
			System.arraycopy(vertices, 3, transformedVertices, 3, vertices.length-3);
			System.arraycopy(normals, 3, transformedNormals, 3, normals.length-3);
			}
		update();
		}
	/**Call to transfer the transformed vertices and normals to the objects being used by the GPU.
	 * This need only be called externally if the user has manually altered the arrays containing the transformed data.*/
	public void update()
		{
		if(useVBO())
			vboOutdated = true;
		else
			{
			vertexData.put(transformedVertices);
			vertexData.rewind();
			normalData.put(transformedNormals);
			normalData.rewind();
			vboOutdated = false;
			}
		}
	/**Extend the model along surface normals. Use only when smooth normals exist...*/
	public void extendAlongNormalsFrom(ObjModel src, float amount)
		{
		float[] srcVerts = src.getTransformedVertices();
		float[] transformedNormals = src.getTransformedNormals();
		for(int i=0; i<transformedVertices.length; i+=3)
			{
			transformedVertices[i  ] = srcVerts[i  ]+amount*transformedNormals[i];
			transformedVertices[i+1] = srcVerts[i+1]+amount*transformedNormals[i+1];
			transformedVertices[i+2] = srcVerts[i+2]+amount*transformedNormals[i+2];
			}
		if(useVBO())
			vboOutdated = true;
		else
			{
			vertexData.put(transformedVertices);
			vertexData.rewind();
			vboOutdated = false;
			}
		}
	/**Extend the model along surface normals. Use only when smooth normals exist...*/
	public void extendAlongNormals(float amount)
		{
		for(int i=0; i<transformedVertices.length; i+=3)
			{
			transformedVertices[i]+=amount*transformedNormals[i];
			transformedVertices[i+1]+=amount*transformedNormals[i+1];
			transformedVertices[i+2]+=amount*transformedNormals[i+2];
			}
		if(useVBO())
			vboOutdated = true;
		else
			{
			vertexData.put(transformedVertices);
			vertexData.rewind();
			vboOutdated = false;
			}
		}
	
	/**
	 * Debug method, render edges if they exist
	 */
	public void renderEdges()
		{
		if(edgePairs.length > 0)
			{
			ImmediateModeWrapper<?> gl = glUtil.immediateMode();
			gl.glBegin(false, GL.GL_LINES, 3, false, 0, 0);
			for(int i = 0; i<edgePairs.length; i++)
				gl.glVertex3fv(transformedVertices, edgePairs[i]*3);
			gl.glEnd();
			}
		}
	/**
	 * Render a part of the model geometry as it is now.
	 */ 
	public void renderMaterial(int materialIndex)
		{
		GL gl = glUtil.getGL();
		if(useVBO())
			{
			bindVBO();
			int i = materialIndex;
			int start = faceMaterialIndices[i];
			int end = (i+1==faceMaterials.length)?faceTuples.length:faceMaterialIndices[i+1];

			if(useMaterials)
				faceMaterials[i].enable(glUtil, 0);
			gl.glDrawElements(GL.GL_TRIANGLES, end-start, GL.GL_UNSIGNED_INT, start*Calc.bytesPerInt);
			if(useMaterials)
				faceMaterials[i].disable(glUtil);
			
			unbindVBO();
			}
		else
			{
			bindVA();
			// This is the actual draw command
			int i = materialIndex;
			int start = faceMaterialIndices[i];
			int end = (i+1==faceMaterials.length)?faceTuples.length:faceMaterialIndices[i+1];
			int length = end-start;
			//Select position in index buffer
			indexData.position(start);
			if(useMaterials)
				faceMaterials[i].enable(glUtil, 0);
			((GL2)gl).glDrawElements(GL.GL_TRIANGLES,length, GL.GL_UNSIGNED_INT, indexData);
			if(useMaterials)
				faceMaterials[i].disable(glUtil);
			unbindVA();
			}
		}
	/**
	 * Render the model as it is now.
	 */
	public void render()
		{
		GL gl = glUtil.getGL();
		if(useVBO())
			{
			bindVBO();
			// This is the actual draw command
			if(useMaterials && mtllib!=null)
				{
				int start, end;
				for(int i=0; i<faceMaterials.length; i++)
					{
					start=faceMaterialIndices[i];
					end=(i+1==faceMaterials.length)?faceTuples.length:faceMaterialIndices[i+1];
					faceMaterials[i].enable(glUtil,0);
					gl.glDrawElements(GL2.GL_TRIANGLES, end-start, GL2.GL_UNSIGNED_INT, start*Calc.bytesPerInt);
					faceMaterials[i].disable(glUtil);
					}
				faceMaterials[faceMaterials.length-1].disable(glUtil);
				}
			else
				{
				gl.glDrawElements(GL2.GL_TRIANGLES, faceTuples.length, GL2.GL_UNSIGNED_INT, 0);
				}
			unbindVBO();
			}
		else
			{
			bindVA();
			// This is the actual draw command
			int start, end;
			for(int i=0; i<faceMaterials.length; i++)
				{
				start=faceMaterialIndices[i];
				end=(i+1==faceMaterials.length)?faceTuples.length:faceMaterialIndices[i+1];
				int length = end-start;
				//Select position in index buffer
				indexData.position(start);
				if(useMaterials)
					faceMaterials[i].enable(glUtil, 0);
				((GL2)gl).glDrawElements(GL2.GL_TRIANGLES,length, GL2.GL_UNSIGNED_INT, indexData);
				if(useMaterials)
					faceMaterials[i].disable(glUtil);
				}
			unbindVA();
			}
		}

	private void bindVBO()
		{
		if(vboOutdated)
			{
			glUtil.getGL().glBindBuffer(GL.GL_ARRAY_BUFFER, vertexVbo);
			glUtil.getGL().glBufferData(GL.GL_ARRAY_BUFFER, transformedVertices.length*Calc.bytesPerFloat, vertexData, GL.GL_DYNAMIC_DRAW);
			glUtil.getGL().glBindBuffer(GL.GL_ARRAY_BUFFER, normalVbo);
			glUtil.getGL().glBufferData(GL.GL_ARRAY_BUFFER, transformedNormals.length*Calc.bytesPerFloat, normalData, GL.GL_DYNAMIC_DRAW);
			glUtil.getGL().glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
			vboOutdated=false;
			}
		if(glUtil.getGL().isGL3())
			{
			EGL3 gl3Util = ((EGL3) glUtil);
			GL3 gl3 = gl3Util.getGL();
			
			//TODO use the methods in ShaderProgram
			int shader = gl3Util.getCurrentProgram().program;

			int globalColorLocation = gl3.glGetUniformLocation( shader, "global_color" );
			gl3.glUniform4fv(globalColorLocation, 1, gl3Util.getActiveColor(), 0);
			
			int colorCountLoc = gl3.glGetUniformLocation( shader, "color_count" );
			int normalCountLoc = gl3.glGetUniformLocation( shader, "normal_count" );
			int texCoordCountLoc = gl3.glGetUniformLocation( shader, "texcoord_count" );

			int projectionMatrixLocation = gl3.glGetUniformLocation( shader, "projection_matrix" );
			int modelviewMatrixLocation = gl3.glGetUniformLocation( shader, "modelview_matrix" );
			int textureMatrixLocation = gl3.glGetUniformLocation( shader, "texture_matrix" );

			float[] pm = gl3Util.projectionMatrix().getArray().clone();
			MatrixStack.transpose4x4f(pm);
			float[] mm = gl3Util.modelviewMatrix().getArray().clone();
			MatrixStack.transpose4x4f(mm);
			float[] tm = gl3Util.textureMatrix().getArray().clone();
			MatrixStack.transpose4x4f(tm);
			gl3.glUniformMatrix4fv(projectionMatrixLocation, 1, false, pm, 0);
			gl3.glUniformMatrix4fv(modelviewMatrixLocation, 1, false, mm, 0);
			gl3.glUniformMatrix4fv(textureMatrixLocation, 1, false, tm, 0);

			int vertexLoc = gl3.glGetAttribLocation( shader, "vertex_position" );
			gl3.glBindBuffer(GL2.GL_ARRAY_BUFFER, vertexVbo);
			gl3.glEnableVertexAttribArray( vertexLoc );
			gl3.glVertexAttribPointer( vertexLoc, 3, GL.GL_FLOAT, false, vertexStride, 0 );

			int normalLoc = gl3.glGetAttribLocation( shader, "vertex_normal" );
			if(normalLoc != -1) 
				{
				gl3.glBindBuffer(GL2.GL_ARRAY_BUFFER, normalVbo);
				gl3.glEnableVertexAttribArray( normalLoc );
				gl3.glVertexAttribPointer( normalLoc, 3, GL.GL_FLOAT, false, vertexStride, 0 );
				}
			gl3.glUniform1i(normalCountLoc, 3);

			/*int colorLoc = gl3.glGetAttribLocation( shader, "vertex_color" );
			/if(colorLoc != -1) 
				{
				gl3.glEnableVertexAttribArray( colorLoc );
				gl3.glVertexAttribPointer( colorLoc, Math.max(1,nColorComps), GL.GL_FLOAT, false, vertexStride, 0 );
				}*/
			gl3.glUniform1i(colorCountLoc, 0);

			int texCoordLoc = gl3.glGetAttribLocation( shader, "vertex_texcoord" );
			if(texCoordLoc != -1) 
				{
				gl3.glBindBuffer(GL2.GL_ARRAY_BUFFER, texCoordVbo);
				gl3.glEnableVertexAttribArray( texCoordLoc );
				gl3.glVertexAttribPointer( texCoordLoc, Math.max(1,nTexCoords), GL.GL_FLOAT, false, vertexStride, 0 );
				}
			gl3.glUniform1i(texCoordCountLoc, nTexCoords);
			
			
			gl3.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, indexVbo);

			int samplerLoc = gl3.glGetUniformLocation(shader, "sampler0");
			//Note. Data structure supports two sets of texture coordinates, but only one is used here.
			gl3.glUniform1i(samplerLoc, 0);
			}
		else if(glUtil.getGL().isGL2())
			{
			GL2 gl = (GL2) glUtil.getGL();
			
			gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, texCoordVbo);
			gl.glTexCoordPointer(nTexCoords,GL.GL_FLOAT, nTexCoords*Calc.bytesPerFloat, 0);
	
			if(nTexCoords2 != 0)
				{
				gl.glClientActiveTexture(GL2.GL_TEXTURE1);
				gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, texCoordVbo2);
				gl.glTexCoordPointer(nTexCoords2,GL.GL_FLOAT, nTexCoords2*Calc.bytesPerFloat, 0);
				gl.glClientActiveTexture(GL2.GL_TEXTURE0);
				}
			gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, normalVbo);
			gl.glNormalPointer(GL.GL_FLOAT, 3*Calc.bytesPerFloat, 0);
			gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, vertexVbo);
			gl.glVertexPointer(3, GL.GL_FLOAT, 3*Calc.bytesPerFloat, 0);
			
			gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, indexVbo);
			gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
			gl.glEnableClientState(GL2.GL_NORMAL_ARRAY);
			gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
			if(nTexCoords2 != 0)
				{
				gl.glClientActiveTexture(GL2.GL_TEXTURE1);
				gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
				gl.glClientActiveTexture(GL2.GL_TEXTURE0);
				}
			}
		else
			System.err.println("Rendering not implemented for: "+glUtil.getGL());
		}
	
	private void unbindVBO()
		{
		if(glUtil.getGL().isGL3())
			{
			GL3 gl3 = (GL3)glUtil.getGL();
			gl3.glDisableVertexAttribArray( 0 );
			gl3.glDisableVertexAttribArray( 1 );
			//gl3.glDisableVertexAttribArray( 2 );
			gl3.glDisableVertexAttribArray( 3 );
			}
		else if(glUtil.getGL().isGL2())
			{
			GL2 gl = (GL2) glUtil.getGL();
			gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
			gl.glDisableClientState(GL2.GL_NORMAL_ARRAY);
			gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
			if(nTexCoords2 != 0)
				{
				gl.glClientActiveTexture(GL2.GL_TEXTURE1);
				gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
				gl.glClientActiveTexture(GL2.GL_TEXTURE0);
				}
			gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
			gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, 0);
			}
		}
	

	private void bindVA()
		{
		GL2 gl = (GL2)glUtil.getGL();
		if(vboOutdated)
			{
			vertexData.put(transformedVertices);
			vertexData.rewind();
			normalData.put(transformedNormals);
			normalData.rewind();
			texCoordData.put(texCoords);
			texCoordData.rewind();
			indexData.put(faceTuples);
			indexData.rewind();
			}
		vboOutdated=false;
		
		gl.glTexCoordPointer(nTexCoords,GL.GL_FLOAT, nTexCoords*Calc.bytesPerFloat, texCoordData);
		if(nTexCoords2 != 0)
			{
			gl.glClientActiveTexture(GL2.GL_TEXTURE1);
			gl.glTexCoordPointer(nTexCoords2,GL.GL_FLOAT, nTexCoords2*Calc.bytesPerFloat, texCoordData2);
			gl.glClientActiveTexture(GL2.GL_TEXTURE0);
			}
		gl.glNormalPointer(GL.GL_FLOAT, 3*Calc.bytesPerFloat, normalData);
		gl.glVertexPointer(3, GL.GL_FLOAT, 3*Calc.bytesPerFloat, vertexData);
		
		gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL2.GL_NORMAL_ARRAY);
		gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
		if(nTexCoords2 != 0)
			{
			gl.glClientActiveTexture(GL2.GL_TEXTURE1);
			gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
			gl.glClientActiveTexture(GL2.GL_TEXTURE0);
			}
		}
	private void unbindVA()
		{
		GL2 gl = (GL2)glUtil.getGL();
		indexData.rewind();
		
		gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL2.GL_NORMAL_ARRAY);
		gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
		if(nTexCoords2 != 0)
			{
			gl.glClientActiveTexture(GL2.GL_TEXTURE1);
			gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
			gl.glClientActiveTexture(GL2.GL_TEXTURE0);
			}
		}

	@Override
	public void finalize()
		{
		if(isDisposed)
			System.err.println("Model "+objName+" already disposed!");
		else if(useVBO() && vbosBound)
			{
			//GC runs in a different thread, so accessing the GL here is unsafe.
			glUtil.invokeLater(new Runnable()
				{
				@Override
				public void run()
					{
					glUtil.getGL().glDeleteBuffers(3, new int[]{vertexVbo, normalVbo, texCoordVbo, indexVbo}, 4);
					if(texCoordVbo2 != -1 && texCoordVbo2Bound)
						glUtil.getGL().glDeleteBuffers(3, new int[]{texCoordVbo2}, 1);
					}
				});
			vbosBound = false;
			texCoordVbo2Bound = false;
			}
		isDisposed = true;
		}

	/**Set the armature. The armature must have completely identical bones, but may have different animation data.*/
	public void setArmature(Armature a)
		{
		armature = a;
		}
	/**Set the material lib. The material lib must contain materials with the same names as the existings ones!*/
	public void setMaterial(String materialName)
		{
		if(materialName == null)
			throw new RuntimeException("Cannot set material to null!");
		Material[] newFaceMaterials = new Material[faceMaterials.length];
		for(int i=0; i<faceMaterials.length; i++)
			{
			if(faceMaterials[i] == null)
				throw new RuntimeException("Cannot change mtllib: Face material "+i+" of "+mtllib+" was null!");
			newFaceMaterials[i] = Mtllib.getMaterial(materialName, faceMaterials[i].name);
			}
		mtllib = materialName;
		faceMaterials = newFaceMaterials;
		}
	public String getMaterial()
		{
		return mtllib;
		}
	public String getDefaultMTLLib()
		{
		return defaultMTLLib;
		}
	/**Returns the verbatim list of materials for the model's triangles. 
	 * Should not be modified externally, use {@link #setMaterial(String)} instead.*/
	public Material[] getFaceMaterials()
		{
		return faceMaterials;
		}
	
	@Override
	public String toString()
		{
		return fname;
		}
	
	public static int id(ObjModel m)
		{
		if(m == null)
			return -1;
		else
			return m.id;
		}
	}